iT邦幫忙

2024 iThome 鐵人賽

DAY 30
0
Software Development

輕鬆學習設計模式Design Pattern系列 第 30

Day 30 依賴反轉原則 Dependency Inversion Principle

  • 分享至 

  • xImage
  •  

在軟體設計中,我們經常面對類別之間的依賴關係。想像你正在開發一個龐大的系統,當中某個小功能需要變更,卻發現一改動就要牽扯到許多不相關的部分。這種情況下你是不是會開始懷疑,設計是否有問題?

這就是設計原則出現的原因,特別是「依賴反轉原則 (Dependency Inversion Principle, DIP)」,它能幫助我們建立靈活的系統,避免高耦合的噩夢。

什麼是依賴反轉原則?

依賴反轉原則是一種解決依賴問題的設計原則,依賴反轉原則強調:高層模組不應依賴於低層模組,兩者都應依賴於抽象。這聽起來可能有點抽象,但換句話說,就是我們要將具體的細節抽象化,讓不同層級的模組之間解耦不直接依賴彼此的實作,進而減少維護成本與錯誤的機會。

依賴反轉原則主要包含兩個重點:

  1. 高層模組不應該依賴於低層模組。
  2. 兩者應該依賴於抽象(介面或抽象類別)。

用最白話的方式來理解,就是「大腦不應該直接控制肌肉運動,而是應該透過神經傳導」。同樣道理,應用程式的高層邏輯不應該直接依賴底層細節,而是應該透過一層抽象來進行溝通。

依賴反轉原則的例子

我們先來看一個不遵守依賴反轉原則的例子。

錯誤的設計,違反依賴反轉原則

想像你正在設計一個點餐系統,在原始設計裡你可能會這樣實作:

class CreditCard {
public:
    void pay() {
        // 信用卡付款邏輯
    }
};

class Order {
    CreditCard creditCard;
public:
    void processOrder() {
        creditCard.pay();
    }
};

這個程式看起來沒什麼問題,但如果突然要新增其他付款方式,例如 PayPal 呢?每當系統需要支援新的付款方式時,你都得改 Order class 類別,這樣的設計很容易隨著功能擴充而變得脆弱。

在上面的範例中,Order 類別直接依賴於 CreditCard 類別,這就是違反依賴反轉原則的設計。這樣一來,只要 CreditCard 類別有變動,或者你要加入其他付款方式,Order 的實作就需要大幅改動,這導致系統的維護變得更加困難,擴充性也大打折扣。

正確的設計,遵守依賴反轉原則

我們可以透過抽象的方式解決這個問題。這裡的解決方案就是將付款方式抽象出來,讓 Order 類別不再直接依賴於具體的 CreditCard,而是依賴於一個抽象的付款介面:

class PaymentMethod {
public:
    virtual void pay() = 0;
};

class CreditCard : public PaymentMethod {
public:
    void pay() override {
        // 信用卡付款邏輯
    }
};

class PayPal : public PaymentMethod {
public:
    void pay() override {
        // PayPal付款邏輯
    }
};

class Order {
    PaymentMethod& paymentMethod;
public:
    Order(PaymentMethod& method) : paymentMethod(method) {}

    void processOrder() {
        paymentMethod.pay();
    }
};

在這個範例中,Order 類別不再依賴具體的 CreditCardPayPal,而是依賴於 PaymentMethod 這個抽象介面。這樣,如果將來要新增其他付款方式,我們只需要實作新的付款類別(實作 PaymentMethod class),而不必動到 Order 類別。這樣的設計就遵循了依賴反轉原則,達到了靈活且可擴展的目的。

為什麼這麼做?

當我們遵循依賴反轉原則,會發現系統變得更加彈性,維護性也提高了。具體實作的變動不會影響高層邏輯,這意味著在不改動核心業務邏輯的情況下,我們可以輕鬆替換或擴充底層的具體實作。

例如在付款系統中,隨著業務發展,可能會有新的付款方式不斷加入。依賴反轉原則讓我們在不改動核心訂單處理的情況下,直接新增其他付款方式,保持系統的穩定性與靈活性。

但這樣做也有一點缺點,就是會讓設計稍微複雜,特別是對於一些小型專案來說,過度抽象反而會增加開發的負擔。但隨著系統成長,這種設計方式能帶來的好處將遠大於初期的設計成本。

實際應用

依賴反轉原則在很多場景中都非常實用,特別是在大型系統或複雜應用中。例如在網路應用中,我們常常會有不同的資料存取方式,如 SQL、NoSQL 或是基於第三方的 API。透過依賴反轉原則,我們可以為高層邏輯設計一個統一的資料存取介面,而實際的資料庫選擇則可以在不同情況下自由切換,而不影響應用層邏輯。

這樣的設計原則也可以應用在許多場景,例如日誌系統、通知系統、甚至是硬體裝置的驅動層。每當你需要擴充或替換具體實作,而不想改動核心邏輯時,依賴反轉原則都是你的好幫手。

更多C++語言相關的文章,歡迎追蹤我的部落格。
https://shengyu7697.github.io/dependency-inversion-principle/


上一篇
Day 29 介面隔離原則 Interface Segregation Principle
系列文
輕鬆學習設計模式Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言